package com.utils.catche;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.example.demo.domain.vo.LogonUser;
import com.example.demo.domain.vo.MenuResource;
import com.example.demo.domain.vo.UserRole;
import com.utils.SecurityUtils;
import com.utils.StringUtils;

@Component
public class SecurityCacheUtils
{
  private static String PREFIX_USER = "SC:U:";
  private static String PREFIX_USER_ID = "SC:U:ID:";
  private static String PREFIX_ROLE = "SC:R:";
  private static String PREFIX_ROLE_ID = "SC:R:ID:";

  private static String PREFIX_MENU = "SC:M:";
  private static String PREFIX_MENU_MAP = "SC:M:M:";
  private static String PREFIX_URL_SET = "SC:L:S:";
  private static String PREFIX_URL = "SC:URL:";
  private static String PREFIX_OPT = "SC:O:";

  private static String PREFIX_MENU_URL = "SC:MU:";

  private static String USER_SET_KEY = "sc:userSet:";
  private static String ROLE_SET_KEY = "sc:roleSet:";

  private static String DOMAIN_ROLE_SET_KEY = "sc:domainRoleSet:";

  private static final ReadWriteLock userLock = new ReentrantReadWriteLock(false);
  private static final ReadWriteLock roleLock = new ReentrantReadWriteLock(false);
  private static CacheSupport securityCache;
  private static int expireTime = 1200;

  @Autowired(required=false)
  @Qualifier("securityCache")
  public void setCacheSupport(CacheSupport cacheSupport)
  {
    securityCache = cacheSupport;
  }

  private static void check() {
    if (securityCache == null)
      throw new RuntimeException("no security cache defined");
  }

  public static void setExpireTime(int expireTime)
  {
    expireTime = expireTime;
  }

  public static LogonUser getLogonUser(String userKey)
  {
    LogonUser user = null;
    userLock.readLock().lock();
    try {
      user = (LogonUser)securityCache.get(PREFIX_USER + userKey, expireTime);
    } finally {
      userLock.readLock().unlock();
    }
    return user;
  }

  public static void cacheUser(LogonUser user)
  {
    check();

    if ((StringUtils.isBlank(user.getRoleKey())) || (!securityCache.hasKey(user.getRoleKey()))) {
      String roleKey = cacheRoles(user.getDomainId(), user.getRoles());
      user.setRoleKey(roleKey);
    }

    if (StringUtils.isBlank(user.getUserKey()))
    {
      String seed = StringUtils.join(new Long[] { user.getUserId(), Long.valueOf(System.nanoTime()) });
      String userKey = EncryptUtils.encode(seed);
      user.setUserKey(userKey);
    }
    LogonUser cachedUser = getLogonUser(user.getUserKey());
    userLock.writeLock().lock();
    try {
      String userKey = PREFIX_USER + user.getUserKey();
      securityCache.put(userKey, user, expireTime);
      if (cachedUser == null)
      {
        securityCache.putSet(PREFIX_USER_ID + user.getUserId(), new String[] { userKey });

        securityCache.putSet(USER_SET_KEY, new String[] { String.valueOf(user.getUserId()) });
      }

    }
    finally
    {
      userLock.writeLock().unlock();
    }
  }

  public static void clearLogonUser(String userId)
  {
    userLock.writeLock().lock();
    try {
      Set userKeys = securityCache.getSet(PREFIX_USER_ID + userId);
      if (userKeys != null) {
        for (Object userKey : userKeys) {
          securityCache.remove(new String[] { userKey.toString() });
        }
        securityCache.remove(new String[] { PREFIX_USER_ID + userId });
      }
    } finally {
      userLock.writeLock().unlock();
    }
  }

  public static void removeLogonUser(String userKey)
  {
    userLock.writeLock().lock();
    try {
      securityCache.remove(new String[] { PREFIX_USER + userKey });
    } finally {
      userLock.writeLock().unlock();
    }
  }

  public static String cacheRoles(Long domainId, List<UserRole> roles)
  {
    String roleKey = StringUtils.join(new Serializable[] { PREFIX_ROLE, domainId, "_", EncryptUtils.md5Digest(CollectionUtils.concat(roles, "roleId")) });

    roleLock.readLock().lock();
    try {
      if (securityCache.hasKey(roleKey)) return roleKey; 
    }
    finally { roleLock.readLock().unlock(); }

    roleLock.writeLock().lock();
    try
    {
      String[] roleIds = CollectionUtils.toStringArray(roles, "roleId");
      securityCache.putSet(roleKey, roleIds);

      for (UserRole role : roles) {
        securityCache.putSet(PREFIX_ROLE_ID + role.getRoleId(), new String[] { roleKey });
      }

      securityCache.putSet(DOMAIN_ROLE_SET_KEY + domainId, new String[] { roleKey });
    }
    finally {
      roleLock.writeLock().unlock();
    }

    cacheRoleMenu(domainId, roleKey);
    return roleKey;
  }

  public static void cacheRoleMenu(Long domainId, String roleKey)
  {
    roleLock.writeLock().lock();
    try
    {
      Set roleIds = securityCache.getSet(roleKey);
      if (roleIds == null)
      {
        return;
      }
      List<MenuResource> unsortMenuResources = SecurityUtils.getRoleResources(roleIds);

      Set urlSet = new HashSet();

      Map operatironMap = new HashMap();

      Map menuMap = new HashMap();

      for (MenuResource menuResource : unsortMenuResources)
      {
        String menuType = menuResource.getType();
        if ("2".equals(menuType))
        {
          String pid = String.valueOf(menuResource.getParentId());
          Set optSet = (Set)operatironMap.get(pid);
          if (optSet == null) optSet = new HashSet();
          optSet.add(menuResource);
          operatironMap.put(pid, optSet);
        } else {
          List menus = (List)menuMap.get(menuResource.getParentId());
          if (menus == null) menus = new ArrayList();
          menus.add(menuResource);
          menuMap.put(menuResource.getParentId(), menus);
        }

        if (StringUtils.isNotBlank(menuResource.getUrl()))
        {
          urlSet.add(menuResource.getUrl());

          securityCache.put(PREFIX_URL + roleKey + menuResource.getUrl(), "1");

          securityCache.put(PREFIX_MENU_URL + menuResource.getId(), menuResource.getUrl());
        }

      }

      securityCache.put(PREFIX_MENU_MAP + roleKey, menuMap);
      securityCache.put(PREFIX_URL_SET + roleKey, urlSet);
      securityCache.put(PREFIX_OPT + roleKey, operatironMap);
    } finally {
      roleLock.writeLock().unlock();
    }
  }

  public static void clearRoleCache(String roleId)
  {
    roleLock.writeLock().lock();
    try
    {
      Set<String> roleKeys = securityCache.getSet(PREFIX_ROLE_ID + roleId);
      if (roleKeys == null) return;
      for (String roleKey : roleKeys) {
        Set<String> urlSet = (Set)securityCache.get(PREFIX_URL_SET + roleKey);
        if (urlSet != null) {
          for (String url : urlSet) {
            securityCache.remove(new String[] { PREFIX_URL + roleKey + url });
          }
        }
        securityCache.remove(new String[] { PREFIX_MENU_MAP + roleKey });
        securityCache.remove(new String[] { PREFIX_URL_SET + roleKey });
        securityCache.remove(new String[] { PREFIX_OPT + roleKey });

        Set<String> menuKeys = securityCache.getSet(PREFIX_MENU + roleKey);
        if (menuKeys != null) {
          for (String menuKey : menuKeys) {
            securityCache.remove(new String[] { menuKey });
          }
        }

        securityCache.remove(new String[] { PREFIX_MENU + roleKey });
        securityCache.remove(new String[] { roleKey });
      }
      securityCache.remove(new String[] { PREFIX_ROLE_ID + roleId });
    } finally {
      roleLock.writeLock().unlock();
    }
  }

  private static Map<String, List<MenuResource>> getRoleMenuMap(String roleKey)
  {
    return (Map)securityCache.get(PREFIX_MENU_MAP + roleKey);
  }

  public static List<MenuResource> getCachedRoleMenus(String roleKey, String parentId, Boolean recursive)
  {
    String menuKey = PREFIX_MENU + roleKey + ":" + parentId + recursive;
    List cachedMenus = (List)securityCache.get(menuKey);
    if (cachedMenus == null) {
      Map menuMap = getRoleMenuMap(roleKey);
      cachedMenus = getMenuResourceFromMap(menuMap, roleKey, parentId, recursive.booleanValue());
      securityCache.put(menuKey, cachedMenus);
      securityCache.putSet(PREFIX_MENU + roleKey, new String[] { menuKey });
    }
    return cachedMenus;
  }

  private static List<MenuResource> getMenuResourceFromMap(Map<String, List<MenuResource>> menuMap, String roleKey, String parentId, boolean recursive)
  {
    List<MenuResource> menuResources = (List)menuMap.get(parentId);
    if (menuResources == null) menuResources = new ArrayList();
    if ((!menuResources.isEmpty()) && (recursive)) {
      for (MenuResource menuResource : menuResources) {
        List childs = getMenuResourceFromMap(menuMap, roleKey, menuResource.getId(), recursive);
        menuResource.setChildren(childs);
      }
    }
    return menuResources;
  }

  public static boolean checkRoleUrl(String roleKey, String url)
  {
    return securityCache.hasKey(PREFIX_URL + roleKey + url);
  }

  public static String getMenuUrl(String menuId)
  {
    return (String)securityCache.get(PREFIX_MENU_URL + menuId);
  }
}
